Previous Book Contents Book Index Next

Inside Macintosh: QuickTime /
Chapter 2 - Movie Toolbox / Using the Movie Toolbox


Creating a Movie

Creating a movie involves several steps. You must first create and open the movie file that is to contain the movie. You then create the tracks and media structures for the movie. You then add samples to the media structures. Finally, you add the movie resource to the movie file. The sample program in this section, CreateWayCoolMovie, demonstrates this process.

This program has been divided into several segments. The main segment, CreateMyCoolMovie, creates and opens the movie file, then invokes other functions
to create the movie itself. Once the data has been added to the movie, this function saves the movie in its movie file and closes the file.

The CreateMyCoolMovie function uses the CreateMyVideoTrack
and CreateMySoundTrack functions to create the movie's tracks. The CreateMyVideoTrack function creates the video track and the media that contains the track's data. It then collects sample data in the media by calling the AddVideoSamplesToMedia function. Note that this function uses the Image Compression Manager. The CreateMySoundTrack function creates the sound track and the media that contains the sound. It then collects sample data by calling the AddSoundSamplesToMedia function.

Note
Throughout this volume, sound track refers to a QuickTime movie track that contains sound--as opposed to a soundtrack, which denotes the entire audio presentation of a movie as filmgoers know it. Consequently, a soundtrack may be made up of one or more QuickTime sound tracks.

A Sample Program for Creating a Movie

The CreateWayCoolMovie program consists of a number of segments, many of which are not included in this sample. Omitted segments deal with general initialization logic and other common aspects of Macintosh programming. The HandleEditMenu function, shown in Listing 2-5, has been included here to show how to initialize the Movie Toolbox with the EnterMovies function.

Listing 2-5 Creating a movie: The main program

#include <Types.h>#include <Traps.h>#include <Menus.h>#include <Packages.h>#include <Memory.h>#include <Errors.h>#include <Fonts.h>#include <QuickDraw.h>#include <Resources.h>#include <GestaltEqu.h>#include <FixMath.h>#include <Sound.h>#include <string.h>

#include "Movies.h"#include "ImageCompression.h"   
void CheckError(OSErr error, Str255 displayString)
{
      if (error == noErr) return;
      if (displayString[0] > 0) 
         DebugStr(displayString);
      ExitToShell();
}

void InitMovieToolbox (void)
{
      OSErr err;
   
      InitGraf (&qd.thePort);
      InitFonts ();
      InitWindows ();
      InitMenus ();
      TEInit ();
      InitDialogs (nil);
      err = EnterMovies ();
      CheckError (err, "\pEnterMovies" );
}

void main( void )
{
      InitMovieToolbox ();
      CreateMyCoolMovie ();
}

A Sample Function for Creating and Opening a Movie File

The CreateMyCoolMovie function, shown in Listing 2-6, contains the main logic for this program. This function creates and opens a movie file for the new movie. It then establishes a data reference for the movie's data (note that, if your movie's data is stored in the same file as the movie itself, you do not have to create a data reference--set the data reference to 0). This function then calls two other functions, CreateMyVideoTrack and CreateMySoundTrack, to create the tracks for the new movie. Once the tracks have been created, CreateMyCoolMovie adds the new resource to the movie file and closes the movie file.

Listing 2-6 Creating and opening a movie file

#define kMyCreatorType 'TVOD'

/*
Sample Player's creator type since it is the movie player 
of choice. You can use your own creator type, of course.
*/
#define kPrompt "\pEnter movie file name:"
void CreateMyCoolMovie (void)
{
   Point    where = {100,100};
   SFReply  theSFReply;
   Movie    theMovie = nil;
   FSSpec   mySpec;
   short    resRefNum = 0;
   short    resId = 0;
   OSErr    err = noErr;
SFPutFile (where, "\pEnter movie file name:", 
                     "\pMovie File", nil, &theSFReply);
   if (!theSFReply.good) return;  

   FSMakeFSSpec(theSFReply.vRefNum, 0,
                            theSFReply.fName, &mySpec);

   err = CreateMovieFile (&mySpec, 
                        'TVOD',
                        smCurrentScript, 
                        createMovieFileDeleteCurFile,
                        &resRefNum, 
                        &theMovie );
   CheckError(err, "\pCreateMovieFile");

   CreateMyVideoTrack (theMovie);
   CreateMySoundTrack (theMovie);
   
   err = AddMovieResource (theMovie, resRefNum, &resId,
                               theSFReply.fName);
   CheckError(err, "\pAddMovieResource");
   
   if (resRefNum) CloseMovieFile (resRefNum);
   DisposeMovie (theMovie);
}

A Sample Function for Creating a Video Track in a New Movie

The CreateMyVideoTrack function, shown in Listing 2-7, creates a video track in the new movie. This function creates the track and its media by calling the NewMovieTrack and NewTrackMedia functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. The bulk of this work is done by the AddVideoSamplesToMedia subroutine. Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's InsertMediaIntoTrack function (described on page 2-248).

Listing 2-7 Creating a video track

#define  kVideoTimeScale 600
#define  kTrackStart       0
#define  kMediaStart       0
#define  kFix1             0x00010000
void  CreateMyVideoTrack (Movie theMovie)
{
   Track       theTrack;
   Media       theMedia;
   OSErr       err = noErr;
   Rect        trackFrame = {0,0,100,320};

   theTrack = NewMovieTrack (theMovie, 
                           FixRatio(trackFrame.right,1),
                           FixRatio(trackFrame.bottom,1), 
                           kNoVolume);
   CheckError( GetMoviesError(), "\pNewMovieTrack" );
   
   theMedia = NewTrackMedia (theTrack, VideoMediaType,
                           600, // Video Time Scale
                           nil, 0);
   CheckError( GetMoviesError(), "\pNewTrackMedia" );
   
   err = BeginMediaEdits (theMedia);
   CheckError( err, "\pBeginMediaEdits" );

   AddVideoSamplesToMedia (theMedia, &trackFrame);
   
   err = EndMediaEdits (theMedia);
   CheckError( err, "\pEndMediaEdits" );
   
   err = InsertMediaIntoTrack (theTrack, 0,/* track start time */
                                 0,        /* media start time */
                                 GetMediaDuration (theMedia),
                                 kFix1);
   CheckError( err, "\pInsertMediaIntoTrack" );
}

A Sample Function for Adding Video Samples to a Media

The AddVideoSamplesToMedia function, shown in Listing 2-8, creates video data frames, compresses each frame, and adds the frames to the media. This function creates its own video data by calling the DrawAFrame function. Note that this function does not temporally compress the image sequence; rather, the function only spatially compresses each frame individually.

Listing 2-8 Adding video samples to a media

#define  kSampleDuration      240
      /* video frames last 240 * 1/600th of a second */
#define  kNumVideoFrames      29
#define  kNoOffset            0
#define  kMgrChoose           0
#define  kSyncSample          0
#define  kAddOneVideoSample   1
#define  kPixelDepth          16

void AddVideoSamplesToMedia (Media theMedia, 
                              const Rect *trackFrame)
{
   long                    maxCompressedSize;
   GWorldPtr               theGWorld = nil;
   long                    curSample;
   Handle                  compressedData = nil;
   Ptr                     compressedDataPtr;
   ImageDescriptionHandle  imageDesc = nil;
   CGrafPtr                oldPort;
   GDHandle                oldGDeviceH;
   OSErr                   err = noErr;

   err = NewGWorld (&theGWorld, 
                  16,         /* pixel depth */
                  trackFrame, 
                  nil, 
                  nil, 
                  (GWorldFlags) 0 );
   CheckError (err, "\pNewGWorld");
   
   LockPixels (theGWorld->portPixMap);
err = GetMaxCompressionSize (theGWorld->portPixMap,
                           trackFrame, 
                           0, /* let ICM choose depth */
                           codecNormalQuality, 
                           'rle ', 
                           (CompressorComponent) anyCodec,
                           &maxCompressedSize);
   CheckError (err, "\pGetMaxCompressionSize" );

   compressedData = NewHandle(maxCompressedSize);
   CheckError( MemError(), "\pNewHandle" );
   
   MoveHHi( compressedData );
   HLock( compressedData );
   compressedDataPtr = StripAddress( *compressedData );

   imageDesc = (ImageDescriptionHandle)NewHandle(4);
   CheckError( MemError(), "\pNewHandle" );
      
   GetGWorld (&oldPort, &oldGDeviceH);
   SetGWorld (theGWorld, nil);
   
   for (curSample = 1; curSample < 30; curSample++) 
   {
      EraseRect (trackFrame);
      DrawFrame(trackFrame, curSample);

      err = CompressImage (theGWorld->portPixMap, 
                           trackFrame, 
                           codecNormalQuality,
                           'rle ',
                           imageDesc, 
                           compressedDataPtr );
      CheckError( err, "\pCompressImage" );
      
      err = AddMediaSample(theMedia, 
                           compressedData,
                           0,    /* no offset in data */
                           (**imageDesc).dataSize,  
                           60,   /* frame duration = 1/10 sec */
                           (SampleDescriptionHandle)imageDesc, 
                           1,    /* one sample */
                           0,    /* self-contained samples */
                           nil);
      CheckError( err, "\pAddMediaSample" );
   }
SetGWorld (oldPort, oldGDeviceH);
   
   if (imageDesc) DisposeHandle ((Handle)imageDesc);
   if (compressedData) DisposeHandle (compressedData);
   if (theGWorld) DisposeGWorld (theGWorld);
}

A Sample Function for Creating Video Data for a Movie

The DrawAFrame function, shown in Listing 2-9, creates video data for this movie. This function draws a different frame each time it is invoked, based on the sample number, which is passed as a parameter.

Listing 2-9 Creating video data

void DrawFrame (const Rect *trackFrame,  long curSample)
{
   Str255 numStr;

   ForeColor( redColor );
   PaintRect( trackFrame );

   ForeColor( blueColor );
   NumToString (curSample, numStr);
   MoveTo ( trackFrame->right / 2, trackFrame->bottom / 2);
   TextSize ( trackFrame->bottom / 3);
   DrawString (numStr);
}

A Sample Function for Creating a Sound Track

The CreateMySoundTrack function, shown in Listing 2-10, creates the movie's sound track. This sound track is not synchronized to the video frames of the movie--rather, it is just a separate sound track that accompanies the video data. This function relies upon an 'snd ' resource for its source sound. The CreateMySoundTrack function uses the CreateSoundDescription function to create the sound description structure for these samples.

As with the CreateMyVideoTrack function discussed earlier, this function creates the track and its media by calling the NewMovieTrack and NewTrackMedia functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. This function adds the sound samples using a single invocation of the AddMediaSample function. This is possible because all the sound samples are the same size and rely on the same sample description (the SoundDescription structure). If you use this approach, it is often advisable to break up the sound data in the movie, so that the movie plays smoothly. After you create the movie, you can call the FlattenMovie function (described on page 2-93) to create an interleaved version of the movie. Another approach is to call AddMediaSample multiple times, breaking the sound into multiple chunks at that point.

Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's InsertMediaIntoTrack function (described on page 2-248).

Listing 2-10 Creating a sound track

#define  kSoundSampleDuration  1
#define  kSyncSample 0
#define  kTrackStart 0
#define  kMediaStart 0
#define  kFix1       0x00010000


void CreateMySoundTrack (Movie theMovie)
{
   Track                      theTrack;
   Media                      theMedia;
   Handle                     sndHandle = nil;
   SoundDescriptionHandle     sndDesc = nil;
   long                       sndDataOffset;
   long                       sndDataSize;
   long                       numSamples;
   OSErr                      err = noErr;


   sndHandle = GetResource ('snd ', 128);
   CheckError (ResError(), "\pGetResource" );
   if (sndHandle == nil) return;

   sndDesc = (SoundDescriptionHandle) NewHandle(4);
   CheckError (MemError(), "\pNewHandle" );
   
   CreateSoundDescription (sndHandle, 
                     sndDesc, 
                     &sndDataOffset, 
                     &numSamples, 
                     &sndDataSize );
                     
   theTrack = NewMovieTrack (theMovie, 0, 0, kFullVolume);
   CheckError (GetMoviesError(), "\pNewMovieTrack" );
   
   theMedia = NewTrackMedia (theTrack, SoundMediaType,
                              FixRound ((**sndDesc).sampleRate), 
                              nil, 0);
   CheckError (GetMoviesError(), "\pNewTrackMedia" );

   err = BeginMediaEdits (theMedia);
   CheckError( err, "\pBeginMediaEdits" );

   err = AddMediaSample(theMedia,
               sndHandle,
               sndDataOffset, /* offset in data */
               sndDataSize,
               1,             /* duration of each sound sample */
               (SampleDescriptionHandle) sndDesc,
               numSamples,
               0,             /* self-contained samples */
               nil);
   CheckError( err, "\pAddMediaSample" );
               
   err = EndMediaEdits (theMedia);
   CheckError( err, "\pEndMediaEdits" );

   err = InsertMediaIntoTrack (theTrack, 
                           0,       /* track start time */
                           0,       /* media start time */
                           GetMediaDuration (theMedia),
                           kFix1);
   CheckError( err, "\pInsertMediaIntoTrack" );

   if (sndDesc != nil) DisposeHandle( (Handle)sndDesc);
}

A Sample Function for Creating a Sound Description Structure

The CreateSoundDescription function, shown in Listing 2-11, creates a sound description structure that correctly describes the sound samples obtained from the 'snd ' resource. This function can handle all the sound data formats that are possible in the sound resource. This function uses the GetSndHdrOffset function to locate the sound data in the sound resource.

Listing 2-11 Creating a sound description

/* Constant definitions */
/* 
   for the following constants, please consult the Macintosh
   Audio Compression and Expansion Toolkit
*/
#define kMACEBeginningNumberOfBytes  6
#define kMACE31MonoPacketSize  2
#define kMACE31StereoPacketSize  4
#define kMACE61MonoPacketSize  1
#define kMACE61StereoPacketSize  2

void CreateSoundDescription (Handle sndHandle,
                        SoundDescriptionHandlesndDesc,
                        long *sndDataOffset,
                        long *numSamples,
                        long *sndDataSize )
{
   long                 sndHdrOffset = 0;
   long                 sampleDataOffset;
   SoundHeaderPtr       sndHdrPtr = nil;
   long                 numFrames;
   long                 samplesPerFrame;
   long                 bytesPerFrame;
   SignedByte           sndHState;
   SoundDescriptionPtr  sndDescPtr;
   
   *sndDataOffset = 0;
   *numSamples = 0;
   *sndDataSize = 0;

   SetHandleSize( (Handle)sndDesc, sizeof(SoundDescription) );
   CheckError(MemError(),"\pSetHandleSize"); 
sndHdrOffset = GetSndHdrOffset (sndHandle);
   if (sndHdrOffset == 0) CheckError(-1,  "\pGetSndHdrOffset ");

            /* we can use pointers since we don't move memory */
   sndHdrPtr = (SoundHeaderPtr) (*sndHandle + sndHdrOffset);
   sndDescPtr = *sndDesc;
   
   sndDescPtr->descSize = sizeof (SoundDescription);
            /* total size of sound description structure */
   sndDescPtr->resvd1 = 0;
   sndDescPtr->resvd2 = 0;
   sndDescPtr->dataRefIndex = 1;
   sndDescPtr->compressionID = 0;
   sndDescPtr->packetSize = 0;
   sndDescPtr->version = 0;
   sndDescPtr->revlevel = 0;
   sndDescPtr->vendor = 0;  
   
   switch  (sndHdrPtr->encode) 
   {
      case stdSH:
         sndDescPtr->dataFormat = 'raw ';
            /* uncompressed offset-binary data */
         sndDescPtr->numChannels = 1;
            /* number of channels of sound */
         sndDescPtr->sampleSize = 8;
            /* number of bits per sample */
         sndDescPtr->sampleRate = sndHdrPtr->sampleRate;
            /* sample rate */
         *numSamples    = sndHdrPtr->length;
         *sndDataSize   = *numSamples;
         bytesPerFrame  = 1;      
         samplesPerFrame = 1;
         sampleDataOffset = (Ptr)&sndHdrPtr->sampleArea
                                             - (Ptr)sndHdrPtr;
         break;   

      case extSH:
      {
         ExtSoundHeaderPtr    extSndHdrP;

         extSndHdrP = (ExtSoundHeaderPtr)sndHdrPtr;
sndDescPtr->dataFormat = 'raw ';
                           /* uncompressed offset-binary data */
         sndDescPtr->numChannels = extSndHdrP->numChannels;
                           /* number of channels of sound */
         sndDescPtr->sampleSize = extSndHdrP->sampleSize;
                           /* number of bits per sample */
         sndDescPtr->sampleRate = extSndHdrP->sampleRate; 
                           /* sample rate */
         numFrames = extSndHdrP->numFrames;
         *numSamples = numFrames;
         bytesPerFrame = extSndHdrP->numChannels * 
                                 ( extSndHdrP->sampleSize / 8);
         samplesPerFrame = 1;
         *sndDataSize = numFrames * bytesPerFrame;
         sampleDataOffset = (Ptr)(&extSndHdrP->sampleArea)
                                              - (Ptr)extSndHdrP;
      }
         break;
         
      case cmpSH:
      {
         CmpSoundHeaderPtr cmpSndHdrP;

         cmpSndHdrP = (CmpSoundHeaderPtr)sndHdrPtr;
         sndDescPtr->numChannels = cmpSndHdrP->numChannels;
               /* number of channels of sound */
         sndDescPtr->sampleSize = cmpSndHdrP->sampleSize;
               /* number of bits per sample before compression */
         sndDescPtr->sampleRate = cmpSndHdrP->sampleRate;
               /* sample rate */
         numFrames = cmpSndHdrP->numFrames; 
         sampleDataOffset =(Ptr)(&cmpSndHdrP->sampleArea) 
                                             - (Ptr)cmpSndHdrP;
         switch (cmpSndHdrP->compressionID) 
         {
            case threeToOne:
               sndDescPtr->dataFormat = 'MAC3';
               /* compressed 3:1 data */
               samplesPerFrame = kMACEBeginningNumberOfBytes;   
               *numSamples = numFrames * samplesPerFrame;
               switch (cmpSndHdrP->numChannels) 
               {
                  case 1:
                     bytesPerFrame = cmpSndHdrP->numChannels 
                                    * kMACE31MonoPacketSize;
                     break;
                  case 2:
                     bytesPerFrame = cmpSndHdrP->numChannels 
                                    * kMACE31StereoPacketSize;
                     break;
                  default: 
                     CheckError(-1, "\pCorrupt sound data" );
                     break;
               }
               *sndDataSize = numFrames * bytesPerFrame;
               break;
            case sixToOne:
               sndDescPtr->dataFormat = 'MAC6';  
               /* compressed 6:1 data */
               samplesPerFrame = kMACEBeginningNumberOfBytes;  
               *numSamples = numFrames * samplesPerFrame;
               switch (cmpSndHdrP->numChannels) 
               {
                  case 1:
                     bytesPerFrame = cmpSndHdrP->numChannels 
                                    * kMACE61MonoPacketSize;  
                     break;
                  case 2:
                     bytesPerFrame = cmpSndHdrP->numChannels 
                                       * kMACE61StereoPacketSize;  
                     break;
                  default:
                     CheckError(-1, "\pCorrupt sound data" );
                     break;
               }
               *sndDataSize = (*numSamples) * bytesPerFrame;
               break;
            default:
               CheckError(-1, "\pCorrupt sound data" );
               break;
         }
         }        /* switch cmpSndHdrP->compressionID:*/
         break;   /* of cmpSH: */
         
      default:
         CheckError(-1, "\pCorrupt sound data" );
         break;
         
   }              /* switch sndHdrPtr->encode */
   *sndDataOffset = sndHdrOffset + sampleDataOffset;  
}

Parsing a Sound Resource

The GetSndHdrOffset function, shown in Listing 2-12, parses the specified sound resource and locates the sound data stored in the resource. The GetSndHdrOffset function cruises through a specified 'snd ' resource. It locates the sound data, if any, and returns its type, offset, and size into the resource.

The GetSndHdrOffset function returns an offset instead of a pointer so that the data is not locked in memory. By returning an offset, the calling function can decide when and if it wants the resource locked down to access the sound data.

The first step in finding this data is to determine if the 'snd ' resource is format (type) 1 or format (type) 2. A type 2 is easy, but a type 1 requires that you find the number of 'snth' resource types specified and then skip over each one, including the init option. Once you do this, you have a pointer to the number of commands in the 'snd ' resource. When the function finds the first one, it examines the command to find out if it is a sound data command. Since it is a sound resource, the command also has its dataPointerFlag parameter set to 1. When the function finds a sound data command, it returns its offset and type, and exits.

WARNING
Do not send the GetSndHdrOffset function a nil handle; if you do, your system will crash.
Listing 2-12 Parsing a sound resource

typedef SndCommand *SndCmdPtr;

typedef struct 
{
   short    format;
   short    numSynths;
} Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;

typedef struct 
{
   short    format;
   short    refCount;
} Snd2Header, *Snd2HdrPtr, **Snd2HdrHndl;
typedef struct 
{
   short    synthID;
   long     initOption;
} SynthInfo, *SynthInfoPtr;


long GetSndHdrOffset (Handle sndHandle)
{
   short howManyCmds;
   long sndOffset  = 0;
   Ptr sndPtr;
   
   if (sndHandle == nil) return 0;
   sndPtr = *sndHandle;
   if (sndPtr == nil) return 0;
   
   if ((*(Snd1HdrPtr)sndPtr).format == firstSoundFormat) 
   {
      short synths = ((Snd1HdrPtr)sndPtr)->numSynths;
      sndPtr += sizeof(Snd1Header) + (sizeof(SynthInfo) * synths);
   } else 
   {
      sndPtr += sizeof(Snd2Header);
   }
   
   howManyCmds = *(short *)sndPtr;
   
   sndPtr += sizeof(howManyCmds);
   /* 
   sndPtr is now at the first sound command--cruise all
   commands and find the first soundCmd or bufferCmd
   */
   while (howManyCmds > 0) 
   {
      switch (((SndCmdPtr)sndPtr)->cmd) 
      {
         case (soundCmd + dataOffsetFlag):
         case (bufferCmd + dataOffsetFlag):
            sndOffset = ((SndCmdPtr)sndPtr)->param2;
            howManyCmds = 0;/* done, get out of loop */
            break;
         default:          /* catch any other type of commands */
            sndPtr += sizeof(SndCommand);
            howManyCmds--;
            break;
      }
   }                       /* done with all commands */

   return sndOffset;
}     /* of GetSndHdrOffset */

Previous Book Contents Book Index Next

© Apple Computer, Inc.
6 JUL 1996